//
// Copyright (c) 2011 Alex Nesterenko
//

#include "nane/SoundSystem/XAudio2/XAudio2SoundSystem.h"
#include "nane/core/assert.h"

namespace nane 
{
    ////////////////////////////////////////////////////////////////////////////////////////////////
    struct XAudio2SoundBuffer
        : public ISoundBuffer
    {
        ESoundFormat format;
        uint32 frequency;
        uint32 channels;
        uint32 bitsPerSample;
        uint32 size;
        byte* buffer;
        
        XAudio2SoundBuffer( ESoundFormat _format, uint32 _frequency, uint32 _channels, uint32 _bitsPerSample, uint32 _size )
            : format( _format )
            , frequency( _frequency )
            , channels( _channels )
            , bitsPerSample( _bitsPerSample )
            , size( _size )
            , buffer( NULL )
        {
            
        }
    };
    ////////////////////////////////////////////////////////////////////////////////////////////////
    struct XAudio2SoundSource
        : public ISoundSource
    {
        IXAudio2SourceVoice* pSourceVoice;
    };
    ////////////////////////////////////////////////////////////////////////////////////////////////
    static const WORD c_soundFormatToFormatTag[] = { WAVE_FORMAT_PCM, WAVE_FORMAT_ADPCM, WAVE_FORMAT_WMAUDIO2, WAVE_FORMAT_XMA2 };
    ////////////////////////////////////////////////////////////////////////////////////////////////
    static WORD s_soundFormatToFormatTag( ESoundFormat _format )
    {
        NANE_ASSERT_RV( _format >= ESoundFormat::PCM && _format < ESoundFormat::_max, "ESoundFormat must be valid", WAVE_INVALIDFORMAT );
        return c_soundFormatToFormatTag[_format.raw_value()];
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    XAudio2SoundSystem::XAudio2SoundSystem()
        : m_pXAudio2( NULL )
        , m_pMasteringVoice( NULL )
    {
        
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    XAudio2SoundSystem::~XAudio2SoundSystem()
    {
    
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool XAudio2SoundSystem::initialize()
    {
        ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
        HRESULT hr = XAudio2Create(&m_pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR);
        return SUCCEEDED(hr);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void XAudio2SoundSystem::shutdown()
    {
        m_pXAudio2->Release();
        m_pXAudio2 = NULL;
        ::CoUninitialize();
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool XAudio2SoundSystem::initializeContext( float _speedOfSound )
    {
        NANE_ASSERT_RV( m_pXAudio2 != NULL, "XAudio2SoundSystem must be initialized", false );
        HRESULT hr = m_pXAudio2->CreateMasteringVoice(&m_pMasteringVoice, XAUDIO2_DEFAULT_CHANNELS, XAUDIO2_DEFAULT_SAMPLERATE, 0, 0, NULL);
        return SUCCEEDED(hr);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void XAudio2SoundSystem::shutdownContext()
    {
        NANE_ASSERT_R( m_pMasteringVoice != NULL, "XAudio2SoundSystem context must be initialized" );
        m_pMasteringVoice->DestroyVoice();
        m_pMasteringVoice = NULL;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    ISoundBuffer* XAudio2SoundSystem::createSoundBuffer( ESoundFormat::raw_type _format, uint32 _frequency, uint32 _channels
                                            , uint32 _bitsPerSample, uint32 _size )
    {
        if( _format != ESoundFormat::PCM )
        {
            return NULL;
        }
        XAudio2SoundBuffer* xAudio2SoundBuffer = new XAudio2SoundBuffer(enum_cast<ESoundFormat>(_format), _frequency, _channels, _bitsPerSample, _size);
        return xAudio2SoundBuffer;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    byte* XAudio2SoundSystem::lockSoundBuffer( ISoundBuffer* _buffer )
    {
        NANE_ASSERT_RV( _buffer != NULL, "ISoundBuffer must be valid", NULL );
        XAudio2SoundBuffer* xAudio2SoundBuffer = static_cast<XAudio2SoundBuffer*>(_buffer);
        if( xAudio2SoundBuffer->buffer == NULL )
        {
            xAudio2SoundBuffer->buffer = new byte[xAudio2SoundBuffer->size];
        }
        return xAudio2SoundBuffer->buffer;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void XAudio2SoundSystem::unlockSoundBuffer( ISoundBuffer* _buffer )
    {
        NANE_ASSERT_R( _buffer != NULL, "ISoundBuffer must be valid" );
        // nothing to do
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void XAudio2SoundSystem::releaseSoundBuffer( ISoundBuffer* _buffer )
    {
        NANE_ASSERT_R( _buffer != NULL, "ISoundBuffer must be valid" );
        XAudio2SoundBuffer* xAudio2SoundBuffer = static_cast<XAudio2SoundBuffer*>(_buffer);
        if( xAudio2SoundBuffer->buffer != NULL )
        {
            delete[] xAudio2SoundBuffer->buffer;
        }
        delete xAudio2SoundBuffer;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    ISoundSource* XAudio2SoundSystem::createSoundSource()
    {
        XAudio2SoundSource* xAudio2SoundSource = new XAudio2SoundSource;
        xAudio2SoundSource->pSourceVoice = NULL;
        return xAudio2SoundSource;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool XAudio2SoundSystem::queueSourceBuffer( ISoundSource* _source, ISoundBuffer* _buffer )
    {
        NANE_ASSERT_RV( m_pXAudio2 != NULL, "XAudio2SoundSystem must be initialized", false );
        NANE_ASSERT_RV( _source != NULL, "ISoundSource must be valid", false );
        NANE_ASSERT_RV( _buffer != NULL, "ISoundBuffer must be valid", false );
        XAudio2SoundSource* xAudio2SoundSource = static_cast<XAudio2SoundSource*>(_source);
        XAudio2SoundBuffer* xAudio2SoundBuffer = static_cast<XAudio2SoundBuffer*>(_buffer);
        if( xAudio2SoundBuffer->buffer == NULL )
        {
            return false;
        }
        if( xAudio2SoundSource->pSourceVoice == NULL )
        {
            WAVEFORMATEX waveFormatEx;
            waveFormatEx.wFormatTag = s_soundFormatToFormatTag(xAudio2SoundBuffer->format);
            waveFormatEx.nChannels = static_cast<WORD>(xAudio2SoundBuffer->channels);
            waveFormatEx.nSamplesPerSec = xAudio2SoundBuffer->frequency;
            waveFormatEx.nBlockAlign = xAudio2SoundBuffer->channels * xAudio2SoundBuffer->bitsPerSample / 8;    // for PCM
            waveFormatEx.nAvgBytesPerSec = waveFormatEx.nSamplesPerSec * waveFormatEx.nBlockAlign;      // for PCM
            waveFormatEx.cbSize = sizeof(waveFormatEx);
            HRESULT hr = m_pXAudio2->CreateSourceVoice(&xAudio2SoundSource->pSourceVoice, &waveFormatEx, 0, XAUDIO2_DEFAULT_FREQ_RATIO, NULL, NULL, NULL);
            if( FAILED(hr) )
            {
                return false;
            }
        }
        XAUDIO2_BUFFER bufferDesc;
        bufferDesc.Flags = 0;
        bufferDesc.AudioBytes = xAudio2SoundBuffer->size;
        bufferDesc.pAudioData = xAudio2SoundBuffer->buffer;
        bufferDesc.PlayBegin = 0;
        bufferDesc.PlayLength = 0;
        bufferDesc.LoopBegin = 0;
        bufferDesc.LoopBegin = 0;
        bufferDesc.LoopLength = 0;
        bufferDesc.LoopCount = XAUDIO2_LOOP_INFINITE;
        bufferDesc.pContext = NULL;
        
        HRESULT hr = xAudio2SoundSource->pSourceVoice->SubmitSourceBuffer(&bufferDesc, NULL);
        return SUCCEEDED(hr);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    uint32 XAudio2SoundSystem::getSourceBuffersQueued( ISoundSource* _source )
    {
        NANE_ASSERT_RV( m_pXAudio2 != NULL, "XAudio2SoundSystem must be initialized", 0 );
        NANE_ASSERT_RV( _source != NULL, "ISoundSource must be valid", 0 );
        XAudio2SoundSource* xAudio2SoundSource = static_cast<XAudio2SoundSource*>(_source);
        if( xAudio2SoundSource->pSourceVoice == NULL )
        {
            return 0;
        }
        XAUDIO2_VOICE_STATE state;
        xAudio2SoundSource->pSourceVoice->GetState(&state);
        return state.BuffersQueued;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void XAudio2SoundSystem::clearSourceBufferQueue( ISoundSource* _source )
    {
        NANE_ASSERT_R( m_pXAudio2 != NULL, "XAudio2SoundSystem must be initialized" );
        NANE_ASSERT_R( _source != NULL, "ISoundSource must be valid" );
        XAudio2SoundSource* xAudio2SoundSource = static_cast<XAudio2SoundSource*>(_source);
        if( xAudio2SoundSource->pSourceVoice != NULL )
        {
            xAudio2SoundSource->pSourceVoice->FlushSourceBuffers();
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void XAudio2SoundSystem::play( ISoundSource* _source )
    {
        NANE_ASSERT_R( m_pXAudio2 != NULL, "XAudio2SoundSystem must be initialized" );
        NANE_ASSERT_R( _source != NULL, "ISoundSource must be valid" );
        XAudio2SoundSource* xAudio2SoundSource = static_cast<XAudio2SoundSource*>(_source);
        if( xAudio2SoundSource->pSourceVoice != NULL )
        {
            xAudio2SoundSource->pSourceVoice->Start(0, XAUDIO2_COMMIT_NOW);
        }        
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void XAudio2SoundSystem::stop( ISoundSource* _source )
    {
        NANE_ASSERT_R( m_pXAudio2 != NULL, "XAudio2SoundSystem must be initialized" );
        NANE_ASSERT_R( _source != NULL, "ISoundSource must be valid" );
        XAudio2SoundSource* xAudio2SoundSource = static_cast<XAudio2SoundSource*>(_source);
        if( xAudio2SoundSource->pSourceVoice != NULL )
        {
            xAudio2SoundSource->pSourceVoice->Stop(0, XAUDIO2_COMMIT_NOW);
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void XAudio2SoundSystem::releaseSoundSource( ISoundSource* _source )
    {
        NANE_ASSERT_R( m_pXAudio2 != NULL, "XAudio2SoundSystem must be initialized" );
        NANE_ASSERT_R( _source != NULL, "ISoundSource must be valid" );
        XAudio2SoundSource* xAudio2SoundSource = static_cast<XAudio2SoundSource*>(_source);
        if( xAudio2SoundSource->pSourceVoice != NULL )
        {
            xAudio2SoundSource->pSourceVoice->DestroyVoice();
        }
        delete xAudio2SoundSource;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
}   // namespace nane
